package org.yaac.client.activity;

import static com.google.common.base.Strings.isNullOrEmpty;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

import javax.inject.Inject;

import org.yaac.client.place.StatisticsPlace;
import org.yaac.client.service.StatisticsServiceAsync;
import org.yaac.client.ui.StatisticsView;
import org.yaac.client.ui.StatisticsView.KindBreakdown;
import org.yaac.client.ui.StatisticsView.NamespaceBreakdown;
import org.yaac.client.ui.StatisticsView.PropertyTypeBreakdown;
import org.yaac.client.ui.StatisticsView.Summary;
import org.yaac.client.util.ExceptionManager;
import org.yaac.shared.stat.StatDTO;
import org.yaac.shared.stat.StatDTO.KindWrapper;
import org.yaac.shared.stat.models.PropertyTypePropertyNameKind;


import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;

/**
 * @author Max Zhu (thebbsky@gmail.com)
 *
 */
public class StatisticsActivity extends SinglePlaceActivity<StatisticsPlace> implements StatisticsView.Presenter {

	private final StatisticsView view;
	
	private final StatisticsServiceAsync statisticsService;
	
	private final ExceptionManager exceptionManager;
	
	@Inject
	StatisticsActivity(
			StatisticsView view,  
			StatisticsServiceAsync statisticsService, 
			ExceptionManager exceptionManager) {
		super();
		this.view = view;
		this.statisticsService = statisticsService;
		this.exceptionManager = exceptionManager;
	}

	@Override
	public StatisticsActivity withPlace(StatisticsPlace place) {		
	    return this;
	}
	
	/**
	 * stat DTO returned from server side
	 * 
	 * will be parsed into easy-to-display format for view
	 */
	private StatDTO stat;
	
	/**
	 * Invoked by the ActivityManager to start a new Activity
	 */
	@Override
	public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
		statisticsService.loadStat(new AsyncCallback<StatDTO>() {
			
			@Override
			public void onSuccess(StatDTO result) {
				stat = result;
				initFilters();
				initSummary();
				onFilterChange(null, null);	// init tables and charts
			}

			@Override
			public void onFailure(Throwable caught) {
				exceptionManager.handle(caught);
			}
		});

		view.setPresenter(this);
		containerWidget.setWidget(view.asWidget());
	}
	
	/**
	 * 
	 */
	private void initFilters() {
		// init kind names
		TreeSet<String> kindNames = new TreeSet<String>();
		
		for (Iterable<KindWrapper> kinds : stat.getNsKindsMap().values()) {
			for (KindWrapper kind : kinds) {
				kindNames.add(kind.getKind().getKind_name());
			}
		}
		
		view.initKindNames(kindNames);
		
		// init namespaces	
		view.initNamespaceNames(stat.getNsKindsMap().keySet());
	}
	
	/**
	 * 
	 */
	private void initSummary() {
		view.updateSummary(new Summary(
				stat.getTotal().getTimestamp(), stat.getTotal().getCount(), stat.getTotal().getBytes()));
	}
	
	@Override
	public void onFilterChange(String selectedNamespace, String selectedKind) {
		// namespace and kind breakdown
		{		
			Map<String, NamespaceBreakdown> namespaceBreakdownMap = new HashMap<String, NamespaceBreakdown>();
			Map<String, KindBreakdown> kindBreakdownMap = new HashMap<String, KindBreakdown>();
			
			for (String namespace : stat.getNsKindsMap().keySet()) {
				// filter out namespace
				// shouldn't use Strings.isNullOrEmpty as there is an empty namespace
				if (selectedNamespace != null && !namespace.equals(selectedNamespace)) {
					continue;
				}
				
				NamespaceBreakdown namespaceBreakdown = namespaceBreakdownMap.get(namespace);
				if (namespaceBreakdown == null) {
					namespaceBreakdown = new NamespaceBreakdown(namespace);
					namespaceBreakdownMap.put(namespace, namespaceBreakdown);
				}
				
				for (KindWrapper kindWrapper : stat.getNsKindsMap().get(namespace)) {					
					String kindName = kindWrapper.getKind().getKind_name();
					
					// filter out kind
					if (!isNullOrEmpty(selectedKind) && !selectedKind.equals(kindName)) {
						continue;
					}
					
					KindBreakdown kindBreakdown = kindBreakdownMap.get(kindName);
					if (kindBreakdown == null) {
						kindBreakdown = new KindBreakdown(kindName);
						kindBreakdownMap.put(kindName, kindBreakdown);
					}
					kindBreakdown.addCount(kindWrapper.getKind().getCount());
					kindBreakdown.addSize(kindWrapper.getKind().getBytes());
					kindBreakdown.addRootCount(
							kindWrapper.getRootKind() == null ? 0l : kindWrapper.getRootKind().getCount());
					kindBreakdown.addRootSize(
							kindWrapper.getRootKind() == null ? 0l : kindWrapper.getRootKind().getBytes());
					kindBreakdown.addNonRootCount(
							kindWrapper.getNonRootKind() == null ? 0l : kindWrapper.getNonRootKind().getCount());
					kindBreakdown.addNonRootSize(
							kindWrapper.getNonRootKind() == null ? 0l : kindWrapper.getNonRootKind().getBytes());
					
					// all aggregate to the same namespace
					namespaceBreakdown.addBytes(kindWrapper.getKind().getBytes());
					namespaceBreakdown.addCount(kindWrapper.getKind().getCount());
				}
			}
			
			view.updateNamespaceBreakdown(new ArrayList<NamespaceBreakdown>(namespaceBreakdownMap.values()));
			view.updateKindBreakdown(new ArrayList<KindBreakdown>(kindBreakdownMap.values()));
		}
		
		// property type breakdown 
		{
			Map<String, PropertyTypeBreakdown> breakdownMap = new HashMap<String, PropertyTypeBreakdown>();
			
			for (String namespace : stat.getNsPropertyMap().keySet()) {
				// shouldn't use Strings.isNullOrEmpty as there is an empty namespace
				if (selectedNamespace != null && !namespace.equals(selectedNamespace)) {
					continue;
				}
				
				for (PropertyTypePropertyNameKind prop : stat.getNsPropertyMap().get(namespace)) {
					// filter out kind
					if (!isNullOrEmpty(selectedKind) && !selectedKind.equals(prop.getKind_name())) {
						continue;
					}
					
					PropertyTypeBreakdown breakdown = breakdownMap.get(prop.getProperty_type());
					if (breakdown == null) {
						breakdown = new PropertyTypeBreakdown(prop.getProperty_type());
						breakdownMap.put(prop.getProperty_type(), breakdown);
					}
					breakdown.addCount(prop.getCount());
					breakdown.addBytes(prop.getBytes());
				}	
			}
			
			view.updatePropertyTypeBreakdown(new ArrayList<PropertyTypeBreakdown>(breakdownMap.values()));
		}
	}
}
